﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.algorithms.network.TFIDF;
using snat.model;
using System.Threading.Tasks;

namespace snat.algorithms.node.TFIDF {
    /// <summary>
    /// The main TFIDF node used for forwarding prediction algorithms.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PersonalizedFWDTFIDFNode<T> : TFIDFNode<T> where T : Document {

        private double threshold;
        public PersonalizedFWDTFIDFNode(int _id, string _name, double threshold, int initialCluster = 0)
            : base(_id, _name, initialCluster) {
            this.threshold = threshold;
            fwdCount = new Dictionary<ClusterNode, Dictionary<Document, int>>();
            rcvCount = new Dictionary<Document, int>();
            messagesSentTo = new Dictionary<ClusterNode, int>();
            messagesToMonitor = new List<DocumentPathRecorder>();
            rand = new Random();
        }
        public PersonalizedFWDTFIDFNode(int _id, string _name, int initialCluster = 0)
            : this(_id, _name, 0.35, initialCluster) { }


        public Dictionary<ClusterNode, Dictionary<Document, int>> fwdCount { get; private set; }
        public Dictionary<Document, int> rcvCount { get; private set; }
        public Dictionary<ClusterNode, int> messagesSentTo { get; private set; }

        public List<DocumentPathRecorder> messagesToMonitor;

        private static Random rand;

        public override void OnMessageSend(Message m, BaseNode n) {
            Console.WriteLine(NetworkModel.Instance.MessageProcessedCount);
            bool updateTerms;
            lock (MessagesAddedToTerms) {
                if (!MessagesAddedToTerms.Contains(m.ID)) {
                    //first node to process
                    updateTerms = true;
                    MessagesAddedToTerms.Add(m.ID);
                    //Console.WriteLine("{0}, FIRST!!!!!!LOLROFL!?!!??##!", m.ID);
                } else {
                    //Console.WriteLine("{0}, second :(", m.ID);
                    //message already processed by other node, don't add its words to Terms
                    updateTerms = false;
                }
            }
            //create the document using dirty reflection
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, updateTerms });
            SentDocuments.Add(document);


            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(ReceivedDocuments,
                d => { d.PopulateTFIDF(termsDict); });

            document.PopulateTFIDF(termsDict);
            foreach (Document d in ReceivedDocuments) {
                double cs = document.CosineSimilarity(d);
                if (cs > threshold) {
                    foreach (ClusterNode cn in document.Recipients) {
                        if (!fwdCount.ContainsKey(cn)) {
                            fwdCount.Add(cn, new Dictionary<Document, int>());
                        }
                        if (!fwdCount[cn].ContainsKey(d)) {
                            fwdCount[cn].Add(d, 1);
                        } else {
                            fwdCount[cn][d]++;
                        }
                    }
                    Console.WriteLine("FORWARDED!");
                }
            }

            foreach (ClusterNode node in document.Recipients) {
                if (messagesSentTo.ContainsKey(node)) {
                    messagesSentTo[node]++;
                } else {
                    messagesSentTo.Add(node, 1);
                }
            }
        }

        public override void OnMessageReceive(Message m, BaseNode n) {
            bool updateTerms;
            lock (MessagesAddedToTerms) {
                if (!MessagesAddedToTerms.Contains(m.ID)) {
                    //first node to process
                    updateTerms = true;
                    MessagesAddedToTerms.Add(m.ID);
                    // Console.WriteLine("{0}, FIRST!!!!!!LOLROFL!?!!??##!", m.ID);
                } else {
                    //Console.WriteLine("{0}, second :(", m.ID);
                    //message already processed by other node, don't add its words to Terms
                    updateTerms = false;
                }
            }
            //create the document using dirty reflection
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, updateTerms });
            ReceivedDocuments.Add(document);


            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(ReceivedDocuments,
                d => { d.PopulateTFIDF(termsDict); });
            document.PopulateTFIDF(termsDict);

            bool done = false;
            foreach (Document d in rcvCount.Keys.ToList()) {
                double cs = document.CosineSimilarity(d);
                if (cs > threshold) {
                    rcvCount[d]++;
                    done = true;
                }
            }
            if (!done) {
                rcvCount.Add(document, 1);
            }
        }

        private static TimeSpan oneWeek = new TimeSpan(7, 0, 0, 0, 0);
        public override void NetworkMessageEveryone(Message m) {
            ReceivedDocuments.RemoveAll(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek);
            SentDocuments.RemoveAll(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek);
            // List<Document> toRemove = Predictions.Keys.Where(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek).ToList();
            // foreach (Document d in toRemove) {
            //     wrong++;
            //     Predictions.Remove(d);
            // }
            //toRemove = ForwardedDocuments.Keys.Where(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek).ToList();
            //foreach (Document d in toRemove) {
            //    ForwardedDocuments.Remove(d);
            //}
        }

        public DocumentPathRecorder IsMessageForwarded(PersonalizedFWDTFIDFNode<T> node, int round) {

            if (fwdCount.Count == 0 || !fwdCount.ContainsKey(node)) return null;
            Dictionary<DocumentPathRecorder, Document> similarTo = new Dictionary<DocumentPathRecorder, Document>();

            if (round == 0) {
                Parallel.ForEach<Term>(Terms.Values,
                            term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
                Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
                Parallel.ForEach<Document>(ReceivedDocuments,
                    d => { d.PopulateTFIDF(termsDict); });

                foreach (Document d in ReceivedDocuments) {
                    // make this into a dpr for the sake of using the same data structure similarTo later on.
                    DocumentPathRecorder dpr = new DocumentPathRecorder(d);
                    double mostsimilar = 0;
                    Document mostsimilarDoc = null;
                    foreach (Document d2 in fwdCount[node].Keys) {
                        double cs = d.CosineSimilarity(d2);
                        if (cs > threshold) {
                            if (cs > mostsimilar) {
                                mostsimilar = cs;
                                mostsimilarDoc = d2;
                            }
                        }
                    }
                    similarTo[dpr] = mostsimilarDoc;
                }
            } else {
                foreach (DocumentPathRecorder dpr in messagesToMonitor) {
                    Document d = dpr.getDoc();
                    double mostsimilar = 0;
                    Document mostsimilarDoc = null;
                    foreach (Document d2 in fwdCount[node].Keys) {
                        double cs = d.CosineSimilarity(d2);
                        if (cs > threshold) {
                            if (cs > mostsimilar) {
                                mostsimilar = cs;
                                mostsimilarDoc = d2;
                            }
                        }
                    }
                    similarTo[dpr] = mostsimilarDoc;
                }
            }
            foreach (DocumentPathRecorder dpr in similarTo.Keys.ToList().OrderBy(e => e.getDoc().Date)) {

                //int totalFwded = fwdCount.Keys.Sum(e => fwdCount[e].ContainsKey(d) ? fwdCount[e][d] : 0);

                int totalRcv = rcvCount.Keys.Sum(e => e.CosineSimilarity(dpr.getDoc()) > threshold ? rcvCount[e] : 0);
                if (similarTo[dpr] == null) continue;

                int possibleTimesFwded = 1;
                //int possibleTimesFwded = fwdCount.Keys.Sum(e => fwdCount[e].ContainsKey(similarTo[dpr]) ? 1 : 0);

                //int sentTo = messagesSentTo[node];

                int fwdTo = fwdCount[node][similarTo[dpr]];

                if (totalRcv < 0 || possibleTimesFwded < 0) {
                    Console.WriteLine("Div 0");
                    continue;
                }

                Console.WriteLine("PROBABILITY {0}", ((double)fwdTo / (double)(totalRcv * possibleTimesFwded)));
                if (0 <= Math.Min(1, (double)fwdTo / (double)(totalRcv * possibleTimesFwded))) {
                    return dpr;
                }
            }

            return null;
        }

        /// <summary>
        /// Checks if a message should be deemed as forwarded, based on similarity to previous received documents.
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        public Document fwdedCheck(Message m) {

            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, true });

            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(ReceivedDocuments,
                d => { d.PopulateTFIDF(termsDict); });
            document.PopulateTFIDF(termsDict);

            double mostsimilar = 0;
            Document mostsimilarDoc = null;
            foreach (Document d in ReceivedDocuments) {
                double cs = d.CosineSimilarity(document);
                if (cs > threshold) {
                    if (cs > mostsimilar) {
                        mostsimilar = cs;
                        mostsimilarDoc = d;
                    }
                }
            }

            return mostsimilarDoc;
        }

        public KeyValuePair<DocumentPathRecorder, double> IsMessageForwarded2(PersonalizedFWDTFIDFNode<T> node) {

            if (fwdCount.Count == 0 || !fwdCount.ContainsKey(node)) return new KeyValuePair<DocumentPathRecorder,double>(null, 0);
            Dictionary<DocumentPathRecorder, Document> similarTo = new Dictionary<DocumentPathRecorder, Document>();

            Parallel.ForEach<Term>(Terms.Values,
                        term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(ReceivedDocuments,
                d => { d.PopulateTFIDF(termsDict); });

            foreach (Document d in ReceivedDocuments) {
                // make this into a dpr for the sake of using the same data structure similarTo later on.
                DocumentPathRecorder dpr = new DocumentPathRecorder(d);
                double mostsimilar = 0;
                Document mostsimilarDoc = null;
                foreach (Document d2 in fwdCount[node].Keys) {
                    double cs = d.CosineSimilarity(d2);
                    if (cs > threshold) {
                        if (cs > mostsimilar) {
                            mostsimilar = cs;
                            mostsimilarDoc = d2;
                        }
                    }
                }
                similarTo[dpr] = mostsimilarDoc;
            }

            if (similarTo.Keys.Count > 0) {
                bool stop = true;
                foreach (DocumentPathRecorder dpr in similarTo.Keys) {
                    if (similarTo[dpr] != null) {
                        stop = false;
                    }
                }
                if (stop == true) { return new KeyValuePair<DocumentPathRecorder, double>(null, 0); }
                else {
                    while (true) {
                        double cutoff = rand.NextDouble();
                        foreach (DocumentPathRecorder dpr in similarTo.Keys.ToList().OrderBy(e => e.getDoc().Date)) {

                            //int totalFwded = fwdCount.Keys.Sum(e => fwdCount[e].ContainsKey(d) ? fwdCount[e][d] : 0);

                            int totalRcv = rcvCount.Keys.Sum(e => e.CosineSimilarity(dpr.getDoc()) > threshold ? rcvCount[e] : 0);
                            if (similarTo[dpr] == null) continue;

                            int possibleTimesFwded = 1;
                            //int possibleTimesFwded = fwdCount.Keys.Sum(e => fwdCount[e].ContainsKey(similarTo[dpr]) ? 1 : 0);

                            //int sentTo = messagesSentTo[node];

                            int fwdTo = fwdCount[node][similarTo[dpr]];

                            if (totalRcv < 0 || possibleTimesFwded < 0) {
                                Console.WriteLine("Div 0");
                                continue;
                            }

                            if (cutoff <= Math.Min(1, (double)fwdTo / (double)(totalRcv * possibleTimesFwded))) {
                                return new KeyValuePair<DocumentPathRecorder, double>(dpr, ((double)fwdTo / (double)(totalRcv * possibleTimesFwded)));
                            }
                        }
                    }
                }
            } else { return new KeyValuePair<DocumentPathRecorder, double>(null, 0); }
        }

        public void ResetMe(List<DocumentPathRecorder> list) {
            messagesToMonitor = list;
        }

        public override void NetworkMessageOnce(Message m) {
            base.NetworkMessageOnce(m);
        }
    }
}
